package zjwt

import (
	"errors"
	"fmt"
	"net/http"
	"strings"
	"time"

	"gitee.com/youkelike/ziam/zutils/ginx"
	"gitee.com/youkelike/zlog"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"github.com/google/uuid"
	"github.com/redis/go-redis/v9"
)

var (
	AtKey = []byte("rZXkvNTEyYml0LXNlY3JldC1nZW5lcmF")
	RtKey = []byte("b3BlbmlkLmNvbS9hdXRoL3NhbXBsZS9e")
)

type RedisJWTHandler struct {
	// mockgen -destination="mocks/redis.mock.go" -package=redismock "github.com/redis/go-redis/v9" Cmdable
	cmd   redis.Cmdable
	paths []string
	l     zlog.Logger
}

func NewRedisJWTHandler(l zlog.Logger, cmd redis.Cmdable) Handler {
	return &RedisJWTHandler{
		cmd:   cmd,
		paths: make([]string, 10),
		l:     l,
	}
}

func (h *RedisJWTHandler) SetLoginToken(ctx *gin.Context, uid int64) error {
	ssid := uuid.New().String()
	err := h.SetJWTToken(ctx, uid, ssid)
	if err != nil {
		return err
	}
	err = h.setRefreshToken(ctx, uid, ssid)
	return err
}

func (h *RedisJWTHandler) setRefreshToken(ctx *gin.Context, uid int64, ssid string) error {
	claims := RefreshClaims{
		Ssid: ssid,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24 * 7)),
		},
		Uid: uid,
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
	tokenStr, err := token.SignedString(RtKey)
	if err != nil {
		return err
	}
	ctx.Header("x-refresh-token", tokenStr)
	return nil
}

func (h *RedisJWTHandler) ClearToken(ctx *gin.Context) error {
	ctx.Header("x-jwt-token", "invalid")
	ctx.Header("x-refresh-token", "invalid")

	claims := ctx.MustGet("users").(*UserClaims)
	return h.cmd.Set(ctx, fmt.Sprintf("users:ssid:%s", claims.Ssid),
		"", time.Hour*24*7).Err()
}

func (h *RedisJWTHandler) CheckSession(ctx *gin.Context, ssid string) error {
	val, err := h.cmd.Exists(ctx, fmt.Sprintf("users:ssid:%s", ssid)).Result()
	switch err {
	case redis.Nil:
		return nil
	case nil:
		if val == 0 {
			return nil
		}
		return errors.New("session 已经无效了")
	default:
		return err
	}
}

func (h *RedisJWTHandler) ExtractToken(ctx *gin.Context) string {
	tokenHeader := ctx.GetHeader("Authorization")
	// 方法一：
	// var rawJWT string
	// fmt.Sscanf(tokenHeader, "Bearer %s", &rawJWT)

	// 方法二：
	// segs := strings.Split(tokenHeader, " ")

	// 方法三：
	segs := strings.SplitN(tokenHeader, " ", 2)

	if len(segs) != 2 {
		return ""
	}
	return segs[1]
}

func (h *RedisJWTHandler) SetJWTToken(ctx *gin.Context, uid int64, ssid string) error {
	claims := UserClaims{
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 30)),
		},
		Uid:  uid,
		Ssid: ssid,
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
	tokenStr, err := token.SignedString(AtKey)
	if err != nil {
		return err
	}
	ctx.Header("x-jwt-token", tokenStr)
	return nil
}

func (h *RedisJWTHandler) CheckLoginMdw(ctx *gin.Context) {
	// 不需要登录校验的
	for _, path := range h.paths {
		if ctx.Request.URL.Path == path {
			return
		}
	}

	tokenStr := h.ExtractToken(ctx)
	claims := UserClaims{}
	// ParseWithClaims 里面，一定要传入指针
	token, err := jwt.ParseWithClaims(tokenStr, &claims, func(token *jwt.Token) (interface{}, error) {
		return AtKey, nil
	})
	if err != nil {
		// 没登录
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}

	// err 为 nil，token 不为 nil
	if token == nil || !token.Valid || claims.Uid == 0 {
		// 没登录
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}

	err = h.CheckSession(ctx, claims.Ssid)
	if err != nil {
		// 要么 redis 有问题，要么已经退出登录
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}

	ctx.Set("users", &claims)
}

func (h *RedisJWTHandler) IgnorePaths(path string) Handler {
	h.paths = append(h.paths, path)
	return h
}

func (h *RedisJWTHandler) RefreshToken(ctx *gin.Context) {
	ctx.Request.Context()
	// 这里前端传过来的是 refresh_token
	refreshToken := h.ExtractToken(ctx)
	var rc RefreshClaims
	token, err := jwt.ParseWithClaims(refreshToken, &rc, func(token *jwt.Token) (interface{}, error) {
		return RtKey, nil
	})
	if err != nil || !token.Valid {
		h.l.Errorw("系统异常", "err", err)
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}

	err = h.CheckSession(ctx, rc.Ssid)
	if err != nil {
		// 信息量不足
		h.l.Errorw("系统异常", "err", err)
		// 要么 redis 有问题，要么已经退出登录
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}

	// 生成新的 ssid
	ssid := uuid.New().String()
	// 设置新的 access_token
	err = h.SetJWTToken(ctx, rc.Uid, ssid)
	if err != nil {
		h.l.Errorw("设置 JWT token 出现异常", "err", err)
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}
	ctx.JSON(http.StatusOK, ginx.Result{
		Msg: "刷新成功",
	})
}

func (h *RedisJWTHandler) LogoutJWT(ctx *gin.Context) {
	err := h.ClearToken(ctx)
	if err != nil {
		ctx.JSON(http.StatusOK, ginx.Result{
			Code: 500,
			Msg:  "退出登录失败",
		})
		return
	}
	ctx.JSON(http.StatusOK, ginx.Result{
		Msg: "退出登录OK",
	})
}
